;( function( $ ){

  //default playback UI template
  var _defaultUI =
            '<div id="playback">'+
              '<div id="title-bar">'+
                '<span id="title">$title$</span>'+
                '<span id="playback-button"></span>'+
              '</div>'+
              '<div id="playback-content">'+
                '<div id="buttons">'+
                  '<ul>'+
                    '<li class="button disabled" id="PF"></li>'+
                    '<li class="button disabled" id="FF"></li>'+
                    '<li class="button paused disabled" id="PP"></li>'+
                    '<li class="button disabled" id="NF"></li>'+
                    '<li class="button disabled" id="LF"></li>'+
                  '</ul>'+
                '</div>'+
                '<div id="speed-settings">'+
                    '<ul>'+
                      '<li>0.5x</li>'+
                      '<li id="middle">1x</li>'+
                      '<li>2x</li>'+
                    '</ul>'+
                    '<div id="bar-control">'+
                      '<div id="bar-button"></div>'+
                    '</div>'+
                '</div>'+
                '<div id="config-button" class="expanded"></div>'+
                '<div id="msg-container">'+
                  '<span class="head">Status:&nbsp;</span><span id="player-status">No data available</span>'+
                '</div>'+
                '<div id="config">'+
                  '<div id="payload-container">'+
                    '<span class="head">Form Data:</span>'+
                    '<textarea id="payload" rows="7"></textarea>'+
                  '</div>'+
                '</div>'+
              '</div>'+
            '</div>';

  //returns a string containing all input ids, a hash will be generated to later (while playing the user inputs) ensure we'll play the data on the same form
  function _generateMissingIds(){
      var _ids = "";
      var _inputCount = 0;

      $("form :input").each(function( idx, el ){
        var $this = $(el);
        var _currentId = $this.attr("id");

        //if no id defined, attach one to the current :input
        if( _currentId === undefined || _currentId === ""){
          do{
            _currentId = "id_" + _inputCount;
            _inputCount++;
          }while( document.getElementById(_currentId) );

          $this.attr("id", _currentId);
        }

        _ids += _currentId;
      });

      return _ids;
  }

  /****************** FormRecorder internal variables ******************/

  //playback configuration
  var recorderConfig;

  //timer controlling the playback speed
  var _timer;

  //xml recorded data
  var _xml = "<formrecord>" + "\n";

  //json recorded data
  var _json = new Object();

  //json initialization
  _json.formrecord = [];
  _json.initialdata = [];


  /************************ FormRecorder private methods ************************/

  //add a triggered event to the xml
  function _addXmlItem( event, to ){
      var eventString = "<e";
      var $_el = $("#" + event.target.id);
      var _from = $("#" + event.target.id).data("from");

      eventString +=  " time='" + event.timeStamp + "' target='" + event.target.id + "' type='" + event.type + "'";

      if( to !== undefined ){
        if( event.type === "change" && $_el.attr("type") === "radio" && !_from){
          _from = $_el.val();
        }

        if(_from){
          eventString += " from='" + _from + "'";
        }

        if( event.type === "change" && !_from && ( $_el.attr("type") === "checkbox" ) ){
          $_el.attr("checked") ? _from = "false" : _from = "true";
          eventString += " from='" + _from + "'";
        }

        eventString += " to='" + to + "'";

        if( event.type === "change" && $_el.attr("type") === "radio" ){
          $("input[name='" + $_el.attr('name') + "']").data("from", to);
        }
        else{
          $_el.data("from", to);
        }
      }

      eventString +=  "/>";
      _xml += eventString + "\n";

      if( recorderConfig.debug ){
        console.log( eventString );
      }
  }

  //add a triggered event to the json
  function _addJsonItem( event, to ){
      var eventItem = new Object();

      eventItem.time = event.timeStamp;
      eventItem.target = event.target.id;
      eventItem.type = event.type;

      if( to !== undefined ){
        eventItem.to = to;
      }

      _json.formrecord.push( eventItem );

      if( recorderConfig.debug ){
        console.log( eventItem );
      }
  }

  //add initial input value to the xml
  function _addXmlInitialItem( id, to ){
      var _initialDataString =  "<ini target='" + id + "' to='" + to + "' />";
      _xml += _initialDataString + "\n";

      if( recorderConfig.debug ){
        console.log( _initialDataString );
      }
  }

  //add initial input value to the json
  function _addJsonInitialItem( id, to ){
      var initialItem = new Object();

      initialItem.target = id;
      initialItem.to = to;

      _json.formrecord.initialdata.push( initialItem );

      if( recorderConfig.debug ){
        console.log( initialItem );
      }
  }

  //process every triggered event and reset the timeout timer
  function _processEvent(event){
      var _to;

      _resetTimer(event.data.timeout);

      if(event.type === "change"){
        _to = ( $(event.target).attr("type") === "checkbox" ) ? $(event.target).prop("checked"): $(event.target).val();
      }

      (recorderConfig.format === "xml")? _addXmlItem(event, _to)
                                       : _addJsonItem(event, _to);
  }

  //
  function _processDone(event){
      var _to;
      if(recorderConfig.format === "xml"){
        _addXmlItem(event, _to);
        _xml += "\n" + "</formrecord>";
      }
      else{
        _addJsonItem(event, _to);
      }

      $(window).unbind("unload");
      $("form :input").die('focus blur change click submit');

      _uploadData();
  }

  function _stopProcess(){
      clearTimeout( _timer );
      $("form :input").die('focus blur change click submit');
      $(window).unbind("timeout");
      $(window).unbind("unload");
  }
  //reset timeout
  function _resetTimer(timeout){
      clearTimeout( _timer );

      _timer = setTimeout(function(){
                            $(window).trigger("timeout");
                            _stopProcess();
                         }, timeout);
  }

  //read all input values before start recording
  function _readInitialData(){
      var _ids = _generateMissingIds();

      if(recorderConfig.format === "xml"){
        _xml += "<init ids='" + _ids + "'>";
      }
      else{
        _json.ids = _ids;
      }

      $("form :input").not(":button, :submit, :reset, :hidden").each(function( idx, el ){
        var $this = $(el);
        var _id = $(this).attr("id");
        var _to = ($this.attr("type") === "checkbox" || $this.attr("type") === "radio" )? $this.prop("checked"): $this.val();

        if( _to ){
          (recorderConfig.format === "xml")? _addXmlInitialItem(_id, _to)
                                           : _addJsonInitialItem(_id, _to);
        }
      });

      if(recorderConfig.format === "xml"){
        _xml += "</init>" + "\n";
      }
  }

  //send data via ajax before submit or when timeout
  function _uploadData(){
      var eventData = (recorderConfig.format === "xml")? _xml: _json;
      $.ajax({
        data: eventData,
      });
  }


  /************************ FormRecorder public methods ************************/

  var formrecorder_methods = {

     init : function( options ) {

       return this.each(function(){

         recorderConfig = jQuery.extend({}, $.fn.formrecorder.defaults, options);

         var $this = $(this),
             data = $this.data('formrecorder');

         if ( ! data ) {

           _readInitialData();
           //binding form events to be recorded
           $("form :input").live('focus blur change click submit', {timeout:recorderConfig.timeout*1000}, function(event){ _processEvent(event); });

           //binding exit events
           $(window).bind("unload timeout", function(event) { _processDone(event); });
           $this.live("submit", function(event){ _processDone(event); });

           //initialize the timer
           _resetTimer(recorderConfig.timeout*1000);

           //set content type
           var _ct = (recorderConfig.format === "xml")? ("text/xml"): ("application/json");

           //initialize ajax
           $.ajaxSetup({
               url: recorderConfig.url,
               async: false,
               type: "POST",
               contentType: _ct
           });

           $this.data('formrecorder', {
               target : $this
           });

         }
       });
     },

     destroy : function( ) {

       return this.each(function(){

         var $this = $(this),
             data = $this.data('formrecorder');

         $(window).unbind('.formrecorder');
         //data.formrecorder.remove();
         $this.removeData('formrecorder');

       })

     },

     stop : function(){
       _stopProcess();
     }
  };

  /**************************************************************/
  /********************  FormPlayback ***************************/
  /**************************************************************/

  //defaults + user's options
  var config;

  //current speed
  var _speed;

  //array containing all events in the xml
  var _events = [];

  //timer which trigger timeout event
  var _playbackTimer;

  //plugin status
  var _playbackStatus = "disabled";

  //current event index
  var _currentEvent = -1;

  //is data (xml or json) loaded successfully
  var _dataLoaded = false;

  //balloon template to show on file inputs
  var _balloonTpl =
   '<div class="box-tip">'+
    	'<span class="arrow"></span>'+
    	'<div class="tip-container">'+
    		'<span class="title">File:</span>'+
    		'<span class="text">placeholder</span>'+
    	'</div>'+
    '</div>';

  //used to assign the id of new added balloons
  var _balloonCount = 0;

  //timer to look at "form data (textarea)" value, to load data before "change" event in "form data" triggered
  var _uiTimer;

  /********************  FormPlayback public methods ***************************/

  function _loadInitialData(formData){
      //update form to initial status
      if(config.format === "xml"){
        formData.find('ini').each(function( idx, el ){
          var $this = $(el);
          var $_target = $("#" + $this.attr("target"));
          var _to = $this.attr("to");

          if( $_target.attr("type") === "checkbox" || $_target.attr("type") === "radio" ){
            if(_to === "true"){
              $_target.prop("checked", "checked");
              $_target.attr("initialized", "true");
              $_target.data("iniAttribute", "checked");
              $_target.data("iniValue", "checked");
            }
          }
          else if( $_target.attr("multiple") === "multiple" && _to.search(",") ){
              var _values = _to.split(",");
              $("#" + $this.attr("target") + " option").each(function(idx, option){
                if($.inArray($(option).attr("value"), _values) != -1){
                  var $_this = $(this);
                  $_this.attr("selected", "selected");
                  $_this.attr("initialized", "true");
                  $_this.data("iniAttribute", "selected");
                  $_this.data("iniValue", "selected");
                }
              });
          }
          else{
            $_target.val(_to);
            $_target.prop("checked", "checked");
            $_target.attr("initialized", "true");
            $_target.data("iniAttribute", "value");
            $_target.data("iniValue", _to);
          }
        });
        return true;
      }
      else{
        _updateStatusLabel("Not implemented.");
        return false;
      }
  }

  function _parseXML( xml, ids ){
      var xmlDoc;
      try {
        xmlDoc = $.parseXML( xml )
        var $xml = $( xmlDoc );

        var _ids = $xml.find('init').attr("ids");
        if(ids === _ids){

            if( _loadInitialData($xml) ){

            $xml.find('e').each(function( idx, el ){
              var $this = $(el);
              var event = new Object();

              event.type = $this.attr( 'type' );
              event.target = $this.attr( 'target' );
              event.to = $this.attr( 'to' );
              event.from = $this.attr( 'from' );
              event.timeStamp = $this.attr( 'time' );

              _events.push( event );
            });

            _updateStatusLabel("Data loaded successfully");
            return true;
          }
          else{
            _updateStatusLabel("Error loading initial form data");
            return false;
          }
        }
        else{
          _updateStatusLabel("Form and/or data mismatch");
          return false;
        }
      }
      catch ( e ) {
        _updateStatusLabel("Invalid XML");
        return false;
      }
  }

  function _parseJSON( json, ids ){
      var eventsData;
      try {
        eventsData = $.parseJSON( json );
        _events = eventsData.formrecord;
        _updateStatusLabel("Data loaded successfully");
        return true;
      }
      catch ( e ) {
        _updateStatusLabel("Invalid JSON");
        return false;
      }
  }

  function _startPlayback(){
      _playbackTimer = setInterval( function(){_gotoStep("next")}, _speed*1000 );
      _setPlaybackStatus( "playing" );
      _updatePlayer();
  }

  function _stopPlayback(){
      clearInterval( _playbackTimer );
      _setPlaybackStatus( "paused" );
      _updatePlayer();
  }

  //change playback speed and update the player depending on its original status
  function _changeSpeed( speed ){
      //save the current player status
      var _playStat = _playbackStatus;
      //if it's playing, pause it
      if( _playbackStatus === "playing" ){
        _stopPlayback();
      }

      _speed = speed;
      _updateStatusLabel( "Speed changed to: " + _speed + " seconds." )

      //if was playing, resume with new speed
      if( _playStat === "playing" ){
        _startPlayback();
      }
  }

  function _setPlaybackStatus( status ){
      _playbackStatus = status;

      if(!config.customUI && _dataLoaded){
        if( _playbackStatus === "paused" ){
          $("#PP").removeClass("playing").addClass("paused");
          _updateStatusLabel("Player paused.")
        } else if( _playbackStatus === "playing" ){
          $("#PP").removeClass("paused").addClass("playing");
          _updateStatusLabel("Playing data...")
        }
      }
  }

  function _setCurrentEvent( value ){
    _currentEvent = value;

    if( _currentEvent >= 0 && _currentEvent <= _events.length - 1 ){
      _updatePlayer();
    }

    if( _currentEvent < 0 || _currentEvent >= _events.length - 1 ){
        _stopPlayback();
    }
  }

  //triggered by buttons or called through $('form').formplayback('jumpTo', step);
  function _gotoStep( step ){
      switch( step ){
        case "prev": if( _currentEvent >= 0 ){
                       _triggerEvent( _events[_currentEvent], true );
                     }
                     _currentEvent--;
                     _setCurrentEvent( _currentEvent );
                     break;

        case "next": if( _currentEvent < _events.length - 1 ){
                       _currentEvent++;
                       _triggerEvent( _events[_currentEvent], false );
                       _setCurrentEvent( _currentEvent );
                     }
                     break;
        case "first": _clearForm();
                      _setInitialState();
                      _setCurrentEvent( -1 );
                     break;
        default:     //jump to "last" step
                     do{
                       _gotoStep( 'next' );
                     }while( _currentEvent < _events.length - 1 )
                     break;
      }
  }

  function _resetPlayback(){
      _setCurrentEvent( -1 );
      _speed = config.speed;
      _clearForm();
      _updateStatusLabel("Player reseted.");
  }

  function _clearForm(){
      $("form").each(function(){
        this.reset();
      });
      $(".box-tip").each(function(){
        $(this).remove();
      });
  }

  //set each input to its inital state
  function _setInitialState(){
    $('*[initialized="true"]').each(function(idx, el){
      var $_this = $(this);
      $_this.attr( $_this.data("iniAttribute"), $_this.data("iniValue") );
    });
  }

  //log status to console and status-label (if defined)
  function _updateStatusLabel( text ){
      if(config.statusLabel !== undefined){
        $( config.statusLabel ).text( text );
      }
      if( config.debug ){
        console.log( text );
      }
  }

  function _updatePlayer(){
    //update default UI
    if(!config.customUI && _dataLoaded){
      if( _currentEvent < 0 ){
        $("#PF").addClass("disabled");
        $("#FF").addClass("disabled");
        $("#PP").removeClass("disabled");
        $("#NF").removeClass("disabled");
        $("#LF").removeClass("disabled");
      }
      else if( _currentEvent >= _events.length - 1 ){
        $("#NF").addClass("disabled");
        $("#LF").addClass("disabled");
        $("#PP").addClass("disabled");
        _updateStatusLabel( "Process Complete." );
      }
      else{
        $(".button").removeClass("disabled");
      }
    }
  }

  var _animationLayer = '<div class="anim-box"></div>';
  var _animLayerCount = 0;

  function _animateEvent($_el){
    var $_animbox = $( _animationLayer );
    var _elemOffset = $_el.offset();
    $_animbox.attr('id', 'anim_' + _animLayerCount);
    _animLayerCount++;
    $_animbox.height( $_el.innerHeight() );
    $_animbox.width( $_el.innerWidth() );
    $_animbox.offset({ top: _elemOffset.top, left: _elemOffset.left });
    $('body').append( $_animbox );
    $_animbox.animate({backgroundColor : 'red'}, _speed*1000, function(){ $_animbox.remove(); });
  }

  //trigger event(click, focus, blur, submit) or change field value according to the content of "event" object parameter
  function _triggerEvent( event, isUndo ){
      var $_el = $( "#" + event.target );

      if( config.animate && $_el && _playbackTimer && event.type === "change"){
        _animateEvent( $_el );
      }

      if( _playbackStatus !== "disabled" && _dataLoaded ){
        if( isUndo ){
          _updateStatusLabel( "Undo event " + event.type + " on " + event.target );
        }
        else{
          _updateStatusLabel( "Triggered event " + event.type + " on " + event.target );
        }
      }

      if ( event.type === "change" ){
        if( $_el.attr("type") === "file" ){
          var _x, _y, _balloonId;
          _x = $_el.offset().left;
          _y = $_el.offset().top;

          if( isUndo ){
            $("#" + $_el.data("balloon")).remove();
            if( event.from ){
              _balloonId = _showBalloon( _x, _y, event.from );
              $_el.data("balloon", _balloonId);
            }
            return;
          }
          else{
            _balloonId = _showBalloon( _x, _y, event.to );
            $_el.data("balloon", _balloonId);
            return;
          }
        }

        if( isUndo ){
          if( $_el.attr("multiple") === "multiple" && event.from && event.from.search(",") ){
            var _values = event.from.split(",");
            $("#" + event.target + " option").each(function( idx, option ){
              if( $.inArray($(option).attr("value"), _values) != -1 ){
                $(this).prop("selected", "selected");
              }
              else{
                $(this).removeAttr("selected");
              }
            });
            return;
          }
          if( event.from ){
            if( event.from !== event.to ){
              if( $_el.attr("type") === "checkbox" ){
                event.from === "true" ? $_el.attr("checked", "checked") : $_el.removeAttr("checked");
              }
              else if( $_el.attr("type") === "radio" ){
                $("input[name='" + $_el.attr("name") + "'][value='" + event.from + "']").prop("checked", "checked");
              }
              else{
                $_el.val( event.from );
              }
            }
            else{
              if( $_el.attr("type") === "radio" || $_el.attr("type") === "checkbox" ){
                $_el.removeAttr("checked");
              }
            }
          }
          else{
            $_el.val( "" );
          }
        }
        else{
          if( $_el.attr("multiple") === "multiple" && event.to.search(",") ){
            var _values = event.to.split(",");
            $("#" + event.target + " option").each(function( idx, option ){
              if( $.inArray($(option).attr("value"), _values) != -1 ){
                $(this).prop("selected", "selected");
              }
              else{
                $(this).removeAttr("selected");
              }
            });
            return;
          }
          $_el.val( event.to );
        }
      }
      else{
        //focus, blur, click, submit
        if($_el.attr('type') !== "file" && !isUndo /*&& ( $_el.attr("type") === "radio" || $_el.attr("type") === "checkbox"  )*/ ){
          $_el.trigger( event.type );
        }
      }
  }

  /************** UI functions **************/


  //show balloon on the file input
  function _showBalloon( x, y, filename ){
      var _balloon = _balloonTpl;

      _balloonCount++;

      x = Math.round(x + 40) + "px";
      y = Math.round(y - 40) + "px";

      _balloon = _balloon.replace( "placeholder", filename );
      var $_balloon = $(_balloon);
      $_balloon.appendTo('body');
      $_balloon.attr("id", "balloon_" + _balloonCount);
      $_balloon.attr('style', 'left:'+ x + ';top:' + y + '; z-index:1000;position: absolute;');
      return "balloon_" + _balloonCount;
  }


  //initialize default UI behavior
  function _initUI(){
      //insert default UI

      if( config.uiTitle ){
        _defaultUI = _defaultUI.replace("$title$", config.uiTitle);
      }
      $('body').prepend( _defaultUI );
      var $_playerUI = $('#playback');

      //load data when textarea change
      $("#payload").change(function(){
        if( !_dataLoaded ){
          $("form").formplayback('setData', $("#payload").val())
        }
      });

      _uiTimer = setInterval(function(){
                   if( $("#payload").val() !== "" ){
                     $("form").formplayback('setData', $("#payload").val());
                     if( _dataLoaded ){
                       clearInterval( _uiTimer );
                     }
                   }
                 }, 300);

      //toggle config section
      $('#config-button').click(function(){ _toggleConfigSection(); });

      //set focus to the textare
      $("#payload").focus();

      //toggle play/pause button, and start/stop playing.
      $("#PP").click(function(){
        if( !$(this).hasClass("disabled") ){
          ( $(this).hasClass("paused") )? $("form").formplayback('play'): $("form").formplayback('pause');
        }
      });

      //previous button
      $("#FF").click(function(){
        if( !$(this).hasClass("disabled") ){
          $("form").formplayback('jumpTo', 'prev');
        }
      });

      //first button
      $("#PF").click(function(){
        if( !$(this).hasClass("disabled") ){
          $("form").formplayback('jumpTo', 'first');
        }
      });

      //next button
      $("#NF").click(function(){
        if( !$(this).hasClass("disabled") ){
          $("form").formplayback('jumpTo', 'next');
        }
      });

      //last button
      $("#LF").click(function(){
        if( !$(this).hasClass("disabled") ){
          $("form").formplayback('jumpTo', 'last');
        }
      });

      //speed button behavior
      $("#bar-control").click(function(e){
        var _offset = e.pageX - $("#bar-control").offset().left;
        if( _offset <= 16 ){
          $("#bar-button").css("left", "0px");
          _changeSpeed( 1 );
        }
        else if(_offset > 49){
          $("#bar-button").css("left", "66px");
          _changeSpeed( 0.1 );
        }
        else{
          $("#bar-button").css("left", "33px");
          _changeSpeed( 0.3 );
        }
      });

      //toogle player visibility
      $("#playback").mouseenter(function(){
        if (! _animating ){
          _togglePlayback();
        }
      });
      $("#playback").mouseleave(function(){
        if (! _animating ){
          _togglePlayback();
        }
      });

  }

  //Data section

  function _toggleConfigSection(){
      if( $('#config-button').hasClass("expanded") ){
        $('#config').data("height", $('#config').height());
        $('#config').animate({height:"0px"}, 500, function(){$('#config').css("display", "none")});
        $('#config-button').removeClass("expanded");
      }
      else{
        $('#config').css("display", "block");
        $('#config').animate({ height: $('#config').data("height") + "px"}, 500);
        $('#config-button').addClass("expanded");
      }
  }

  var _animating = false;
  function _togglePlayback(){
      var $_playerUI = $('#playback');
      if( $_playerUI.hasClass("expanded") ){
        $_playerUI.data("top", $_playerUI.offset().top);
        $_playerUI.data("height", $_playerUI.height());
        _animating = true
        $_playerUI.animate({top: $_playerUI.offset().top - $_playerUI.height() + 10 + "px"}, 500, function(){_animating = false});
        $_playerUI.removeClass("expanded");
      }
      else{
        _animating = true
        $_playerUI.animate({ top: ($_playerUI.offset().top - $_playerUI.data("top") + $_playerUI.data("height") -17 ) + "px"}, 500, function(){_animating = false});
        $_playerUI.addClass("expanded");
      }
  }

  /************** FormPlayback public methods **************/

  var formplayback_methods = {
     init : function( options ) {

       return this.each(function(){

         config = jQuery.extend({}, $.fn.formplayback.defaults, options);

         var $this = $(this),
             data = $this.data('formplayback');

         if ( ! data ) {

           if(!config.customUI){
              _initUI();
           }
           _speed = config.speed;

           $this.data('formplayback', {
               target : $this
           });
         }
       });
     },

     play : function(){
        if( _dataLoaded ){
          _startPlayback();
        }
     },

     pause : function(){
        if( _dataLoaded ){
          _stopPlayback();
        }
     },

     // "step" must be one of these values: "prev", "next", "first", "last"
     jumpTo : function( step ){
        if( _dataLoaded ){
          _stopPlayback();
          //jump to the specified speed, if no value supplied the default is "last"
          _gotoStep( step );
        }
     },

     // speed in seconds
     setSpeed : function( speed ){
        _changeSpeed( speed );
     },

     //assign events data, format must be: "xml" or "json"
     setData : function( data, format ){
        _resetPlayback();

        var _ids = _generateMissingIds();

        config.format === "xml" ? _dataLoaded = _parseXML( data, _ids ) : _dataLoaded = _parseJSON( data, _ids );

        if( _dataLoaded ){
          if($('form').formrecorder()){
            $('form').formrecorder('stop');
          }
          _toggleConfigSection();
          _updatePlayer();
        }

     },

     destroy : function() {

       return this.each(function(){

         var $this = $(this),
             data = $this.data('formplayback');

         $(window).unbind('.formplayback');
         data.formrecorder.remove();
         $this.removeData('formplayback');

       })

     }
  };

  /************* plugins ****************/

  $.fn.formrecorder = function( method, options ) {

    if ( formrecorder_methods[method] ) {
      return formrecorder_methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    }
    else if ( typeof method === 'object' || ! method ) {
      return formrecorder_methods.init.apply( this, arguments );
    }
    else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.formrecorder' );
    }

  };

  $.fn.formplayback = function( method, options ) {

    if ( formplayback_methods[method] ) {
      return formplayback_methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    }
    else if ( typeof method === 'object' || ! method ) {
      return formplayback_methods.init.apply( this, arguments );
    }
    else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.Formplayback' );
    }

  };

  /************* plugins defaults ****************/

  $.fn.formrecorder.defaults = {
      timeout   : 10,       //speed in seconds
      format    : "xml",    //"xml" or "json"
      debug     : false
  };

  $.fn.formplayback.defaults = {
      speed     : 1,         //speed in seconds
      format    : "xml",     //"xml" or "json"
      customUI  : false,     //if "false", default UI is loaded
      animate   : true,      //animate fields while playing
      debug     : false,
      uiTitle   : "Form Player"
  };

} )( jQuery );